#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <sys/time.h>
#include <sys/times.h>
//#include <limits.h>
#include <sys/resource.h>
#include "utils.h"
#include "proclist.h"

/*** Global variables ***/
proc710 *runningProcess; 	// Holds the data for the process currently running
proc710 *job_list;					// Initial job list, from the file
volatile int totalProcesses; 		// Total nb of processes from the file
volatile int totDispatched; 		// Total nb of processes dispatched to the realtime or user-priority lists
volatile int currentTime; 			// The scheduler,s current time (ticks)
volatile int jobListEmpty; 			// To check if the initial job list is empty
volatile int rtListEmpty;			// To check if the realtime job list is empty
volatile int userListsEmpty;		// To check if the user-priority job lists (3) is empty
volatile int rtProcRunning;			// To check if their is a realtime process still running
FILE *mainOutput;					// Main output of the program

// FIFO for realtime and user-priority processes
p_list *rtProcList;
p_list *userProcList;

// FIFO for every user-priority list
p_list *uPriorityList1;
p_list *uPriorityList2;
p_list *uPriorityList3;

// Resources
volatile resource710 resources;

/**
	Initializes a proc710
	p HAS TO be reallocated first: (proc710*) malloc(sizeof(struct proc710))
*/
void initProc710(proc710* p)
{
	p->arrivalTime 		= -1;
	p->priority 		= -1;
	p->executionTime 	= -1;
	p->nbPrinters 		= -1;
	p->nbScanners 		= -1;
	p->nbModems 		= -1;
	p->nbCDs 			= -1;
	p->state 			= -1;
	p->pid 				= -1;
}

/**
	NOTE: BEFORE calling this procedure, "runningProcess" MUST be assigned the new process data

	Énoncé:
	- Quand un processus est créé et lancé, fork et exec(‘’log710h14proc’’,…), 
	  l’ordonnanceur affiche les paramètres du processus (ID du processus, priorité, 
	  temps d’exécution restant, ressources requises) avant d’exécuter la commande « exec ».
*/
pid_t startProcess(proc710 *p)
{
	// Create process
	pid_t cpid = fork();
	
	if (cpid == 0) // Child
	{
		// Print information about the process
		fprintf(mainOutput, "PID: %d\n", getpid());
		fprintf(mainOutput, "Priority: %d\n", p->priority);
		fprintf(mainOutput, "Exec. time: %d\n", p->executionTime);
		fprintf(mainOutput, "Resources: Printers(%d), Scanners(%d), Modems(%d), CDs(%d)\n", 
				p->nbPrinters, p->nbScanners, p->nbModems, p->nbCDs);
		fflush(mainOutput);
		
		// Execute the process command
		char execTime[2];
		sprintf(execTime, "%d", p->executionTime);
		execl("./log710h14process", "./log710h14process", (const char*)execTime, (char *) 0);
	}
	else // Parent
	{
		// Return the new launched process' pid
		return cpid;
	}
}

/**
 * Feed the appropriate process queue (real-time or user priority) from the initial job list
 * The arrival time of the process has to come before it is submitted to any scheduling list.
	
	Énoncé:
	- Un processus est prêt quand il est arrivé et toutes les ressources requises sont disponibles
 */
int dispatchProcesses()
{
	proc710 p;
	
	if (totDispatched < totalProcesses)
	{
		if (job_list[totDispatched].arrivalTime <= currentTime)
		{
			p = job_list[totDispatched++];
			
			// Real-time process
			if (p.priority == 0)
			{
				p.state = READY;
				push(rtProcList, p); // Process inserted here are READY
				fflush(mainOutput);
			}
			else
			{
				p.state = NEW;
				push(userProcList, p); // Process inserted here are in state NEW and must wait for resources to be READY
				fflush(mainOutput);
			}
		}
	}
	else // No jobs are left for dispatching. The initial job lis is done.
		jobListEmpty = 1;
	
	return 0;
}

/**
	Énoncé:
	- Les processus « temps réel » en attente d’exécution sont ordonnancés en FCFS
	- Un processus « temps réel » s’exécute jusqu’à sa terminaison. L’ordonnanceur le supprime (‘kill’)
	  quand son temps d’exécution expire en lui envoyant un signal (SIGINT)
	- Quand il n’y a plus aucun processus dans la liste de soumissions, 
	  la file d’entrée et les files de feedback, l’ordonnanceur termine.
	  
	- Les processus temps réel ont la plus haute priorité. Ils doivent être exécutés immédiatement selon 
	  la logique « premier arrivé, premier servi » (FCFS). Ces processus une fois lancés ne sont pas interrompus. 
	  L’ordonnanceur les laisse s’exécuter jusqu’à la fin (ordonnanceur temps réel - FCFS).
	- Les files de soumissions sont alors examinées : tout processus temps réel est exécuté jusqu’à la fin 
	  en interrompant (préemption) tout autre processus en cours d’exécution.
*/
void realTimeScheduling()
{
	int pass = 0;
	rtProcRunning = 1;
	
	if (runningProcess->pid == -1) // No process
		pass = 1;
	else if (runningProcess->priority != 0) // User-priority process
	{
		rtListEmpty = 0;
	
		// Suspend the user-priority process
		kill(runningProcess->pid, SIGTSTP);
		
		// Change the state of the user-priority process
		runningProcess->state = SUSPENDED;
		
		// Wait for the process' response to synchronize outputs with the scheduler
		int status;
		waitpid(runningProcess->pid, &status, WUNTRACED);
		
		pass = 1;
		
		// Re-init the running process: No more running process
		runningProcess = (proc710*) malloc(sizeof(struct proc710));
		initProc710(runningProcess);
	}
	
	if (pass) // No process
	{
		if (rtProcList->size != 0)
		{
			rtListEmpty = 0;
		
			proc710 p;
			
			// Launch real-time process and wait for it to finish
			p = pop(rtProcList);
			
			// Assign the new process to start to running process
			runningProcess->arrivalTime 	= p.arrivalTime;
			runningProcess->priority 		= p.priority;
			runningProcess->executionTime 	= p.executionTime;
			runningProcess->nbPrinters 		= p.nbPrinters;
			runningProcess->nbScanners 		= p.nbScanners;
			runningProcess->nbModems 		= p.nbModems;
			runningProcess->nbCDs 			= p.nbCDs;
			
			// Launch new process
			runningProcess->pid 			= startProcess(runningProcess);
			runningProcess->state 			= RUNNING;
		}
	}
	// One RT process is running
	else if (runningProcess->executionTime <= 0) // Check if it has finished
	{
		// Terminate the realtime process
		kill(runningProcess->pid, SIGINT);
		
		// Wait for the process that has just exited to terminate it
		int status;
		waitpid(runningProcess->pid, &status, WUNTRACED);
		runningProcess->state = FINISHED;
		
		// Re-init the running process: No more running process
		runningProcess = (proc710*) malloc(sizeof(struct proc710));
		initProc710(runningProcess);
		
		rtProcRunning = 0;
	}
	else
		runningProcess->executionTime--; // Decrement the remaining execution time
}

/**
	Énoncé:
	- Un processus est prêt quand il est arrivé et toutes les ressources requises sont disponibles
	- Quand les ressources requises par un processus utilisateur deviennent disponibles, ce processus
	  est transféré vers la file d’attente de priorité appropriée pour être ordonnancé par l’ordonnanceur
	  utilisateur et l’état des ressources disponibles est mis à jour.
	- Un processus utilisateur est autorisé à s’exécuter pendant un pas d’ordonnancement (un quantum de temps)
	  avant d’être suspendu par l’ordonnanceur (signal SIGTSTP) ou terminé (SIGINT) si son temps d’exécution est expiré. 
	  Si le processus utilisateur est suspendu, sa priorité est décrémentée (quand c’est possible) et il est remis dans
	  la file de priorité appropriée. Pour synchroniser les sorties entre l’ordonnanceur et le processus fils, l’ordonnanceur
	  doit attendre que le processus réponde à un signal SIGINT ou SIGTSTP (waitpid(pid, …)).
	- Pourvu qu’il n’y ait pas de processus temps réel en attente, le processus utilisateur de plus haute priorité dans les files
	  feedback est démarrée ou relancée (SIGCONT).
	- Quand un processus est terminé, toutes ses ressources sont retournées pour être allouées à d’autres processus.
	- Quand il n’y a plus aucun processus dans la liste de soumissions, 
	  la file d’entrée et les files de feedback, l’ordonnanceur termine.
	- Les processus utilisateurs normaux sont ordonnancés selon trois niveaux de priorités avec feedback. 
	  Le quantum de temps de base est une 1 seconde qui est également le quantum de l’ordonnanceur de 
	  feedback (ordonnanceur utilisateur – Multi niveau avec retour).
	- Le fonctionnement normal d’une file de feedback est d’accepter les tâches de plus haute priorité et décrémenter 
	  la priorité après chaque quantum complété. L’ordonnanceur peut accepter les tâches de plus basse priorité en les 
	  insérant dans la file appropriée. Ceci permet de simuler un ordonnanceur du type round-robin si toutes les tâches 
	  acceptées sont de priorité minimale.
*/
void userPrioScheduling()
{
	proc710 p;
	proc *current, *temp;

	// Put user process in the appropriate priority list (if resources are available)
	if (userProcList->size != 0)
	{
		current = userProcList->first;
		while (current != NULL/* && go*/)
		{			
			// Check if proc has all required resources
			if (current->process.nbPrinters <= resources.nbPrinters && current->process.nbScanners <= resources.nbScanners &&
				current->process.nbModems <= resources.nbModems && current->process.nbCDs <= resources.nbCDs)
			{
				p = current->process;
				p.state = READY;
				
				// Queue it in the appropriate user priority list
				if (p.priority == 1)
				{
					push(uPriorityList1, p);
				}
				else if (p.priority == 2)
				{
					push(uPriorityList2, p);
				}
				else if (p.priority == 3)
				{
					push(uPriorityList3, p);
				}
				
				// Update resources' usage
				resources.nbPrinters 	-= p.nbPrinters;
				resources.nbScanners 	-= p.nbScanners;
				resources.nbModems 		-= p.nbModems;
				resources.nbCDs 		-= p.nbCDs;
				
				// Remove process from the user-priority general list
				pop(userProcList);
				current = userProcList->first;
			}
			else
			{
				current = current->next;
			}
		}
	}
	
	// Manage the user process currently running
	if (runningProcess->pid != -1) // A user-priority process is running
	{		
		// Decrement its execution time
		runningProcess->executionTime--;
		
		// If not finished
		if (runningProcess->executionTime > 0)
		{		
			// Suspend the user-priority process
			kill(runningProcess->pid, SIGTSTP);
			
			// Wait for the process' response to synchronize outputs with the scheduler
			int status;
			waitpid(runningProcess->pid, &status, WUNTRACED);
			
			// Update the process' state
			runningProcess->state = SUSPENDED;
		
			// Decrement its priority if possible
			if (runningProcess->priority < 3)
			{
				runningProcess->priority++;
			
				// Move it to the appropriate list
				if (runningProcess->priority == 2)
				{
					// Remove process from user-priority list 1
					p = pop(uPriorityList1);
					
					// Push it in the user-priority list 2
					push(uPriorityList2, p);
				}
				else if (runningProcess->priority == 3)
				{
					// Remove process from user-priority list 2
					p = pop(uPriorityList2);
					
					// Push it in the user-priority list 3
					push(uPriorityList3, p);
				}
			}
			else
			{
				// (Round-Robin)
				// Remove process from the head of user-priority list 3
				p = pop(uPriorityList3);
					
				// Push it in the back of user-priority list 3
				push(uPriorityList3, p);
			}
		}
		else // User process has finished
		{
			// Terminate the user process
			kill(runningProcess->pid, SIGINT);
			
			// Release resources
			resources.nbPrinters 	+= runningProcess->nbPrinters;
			resources.nbScanners 	+= runningProcess->nbScanners;
			resources.nbModems 		+= runningProcess->nbModems;
			resources.nbCDs 		+= runningProcess->nbCDs;
			
			// Remove the running process from its present list
			if (runningProcess->priority == 1)
				pop(uPriorityList1);
			else if (runningProcess->priority == 2)
				pop(uPriorityList2);
			else if (runningProcess->priority == 3)
				pop(uPriorityList3);
			
			// Wait for the process to terminate it completely
			int status;
			waitpid(runningProcess->pid, &status, WUNTRACED);
			
			// Re-init the running process: No more running process
			runningProcess = (proc710*) malloc(sizeof(struct proc710));
			initProc710(runningProcess);
		}
	}
	
	// Launch the user processes in ready state
	if (uPriorityList1->size > 0)
	{		
		userListsEmpty = 0;
	
		// Get the first element of the priority list 1
		runningProcess = &uPriorityList1->first->process;
	}
	else if (uPriorityList2->size > 0)
	{		
		userListsEmpty = 0;
		
		// Get the first element of the priority list 2
		runningProcess = &uPriorityList2->first->process;
	}
	else if (uPriorityList3->size > 0)
	{		
		userListsEmpty = 0;
		
		// Get the first element of the priority list 3
		runningProcess = &uPriorityList3->first->process;
	}
	else
		userListsEmpty = 1;
		
	if (userListsEmpty == 0)
	{	
		if (runningProcess->state == READY) // Process READY but not started yet
		{		
			// Start the process
			runningProcess->pid 	= startProcess(runningProcess);
			runningProcess->state 	= RUNNING;
		}
		else if (runningProcess->state == SUSPENDED) // Process already started and SUSPENDED
		{		
			// Continue the process' execution
			kill(runningProcess->pid, SIGCONT);
			
			// Wait for the process' response to synchronize outputs with the scheduler
			int status;
			waitpid(runningProcess->pid, &status, WNOHANG);
			
			// Update the running process' state
			runningProcess->state = RUNNING;
		}
	}
}

/**
	Énoncé:
	- Quand il n’y a plus aucun processus dans la liste de soumissions, 
	  la file d’entrée et les files de feedback, l’ordonnanceur termine.
	- La file « temps réel » doit être vide avant que l’ordonnanceur utilisateur puisse être réactivé.
*/
/*void scheduleProcesses()
{
	int done = 0, go = 0;
	proc *current;
	proc710 p;
	
	while (!done)
	{
		// - Real-time scheduler (interrupt any non-real-time process and execute real-time processes in FCFS)
		if (rtProcList->size != 0)
		{
			realTimeScheduling();
		}
		// - User priority scheduler
		else
		{
			userPrioScheduling();
		}
	}
}*/

int main(int argc, char **argv)
{
	if (argc < 2)
	{
		printf("Usage: Log710Lab2 <jobs_file.txt>\n");
		return 0;
	}
	
	// Set main output
	mainOutput = stdout;
	
	// Scheduler global time
	currentTime = 0;
	
	// Initialize job lists empty state
	jobListEmpty 	= 0;	// To check if the initial job list is empty
	rtListEmpty 	= 0;	// To check if the realtime job list is empty
	userListsEmpty 	= 0;	// To check if the user-priority job lists (3) is empty
	rtProcRunning 	= 0;
	
	// Initialize the running process (none)
	runningProcess = (proc710*) malloc(sizeof(proc710));
	initProc710(runningProcess);
	
	/**
		Le processus est initialement soumis à l’ordonnanceur via une liste de processus
		qui désigne le temps d’arrivée, la priorité, le temps d’exécution, et les ressources requises.
	*/
	// Get the number of jobs (processes) to schedule
	totalProcesses 	= get_nb_jobs(argv[1]);
	printf("Total processes: %d\n\n", totalProcesses);
	
	// Total number of processed actually dispatched to a scheduling queue (real-time, user priority)
	totDispatched	= 0;
	
	// Fill the process submission list from the file
	job_list 		= get_job_list(argv[1], totalProcesses);
	
	// Créer 02 files de soumission pour les procs tps-réels et procs utilisateur
	rtProcList 	 = (p_list*) malloc(sizeof(p_list));
	userProcList = (p_list*) malloc(sizeof(p_list));
	rtProcList->size 	 = 0;
	userProcList->size 	 = 0;
	
	// Créer une file pour chaque priorité de proc utilisateur
	uPriorityList1 = (p_list*) malloc(sizeof(p_list));
	uPriorityList2 = (p_list*) malloc(sizeof(p_list));
	uPriorityList3 = (p_list*) malloc(sizeof(p_list));
	uPriorityList1->size = 0;
	uPriorityList2->size = 0;
	uPriorityList3->size = 0;
	
	// Initialiser les ressources disponibles
	resources.nbPrinters 	= 2;
	resources.nbScanners 	= 1;
	resources.nbModems 		= 1;
	resources.nbCDs 		= 2;
	
	// Initialize vars
	int done = 0, rc;
    long clktck = sysconf(_SC_CLK_TCK);
    struct tms t;
    clock_t starttick, stoptick;
	proc710 p;

	/**
		Énoncé:
		- Quand il n’y a plus aucun processus dans la liste de soumissions, 
		  la file d’entrée et les files de feedback, l’ordonnanceur termine.
	*/
	// At each scheduler execution step
	while (!done)
	{
		starttick = times (&t);        // use timer to ascertain whether 'tick' should be
        rc = sleep(1);                 //  reported
        stoptick = times (&t);
		
		++currentTime;
		//if (rc == 0 || (stoptick - starttick) > (clktck / 2))
        //  fprintf(mainOutput, "tick %d\n", ++currentTime);
	
		// Feed the appropriate process queue (real-time or user priority) from the initial job list
		dispatchProcesses();
		
		// - Real-time scheduler (interrupt any non-real-time process and execute real-time processes in FCFS)
		if (rtProcList->size > 0 || rtProcRunning)
		{
			rtListEmpty = 0;
			// Launch real-time process scheduler
			realTimeScheduling();
		}
		// - User priority scheduler (active only if real-time queue is empty)
		else
		{
			//if (lastRTFinished) // To ensure that the last RT process has finished and been terminated properly
			rtListEmpty = 1; // Realtime process list is empty
		
			// Launch user-priority process scheduler
			userPrioScheduling();
		}
		
		//fprintf(mainOutput, "jobListEmpty:%d -- rtListEmpty:%d -- userListsEmpty:%d\n", jobListEmpty, rtListEmpty, userListsEmpty);
		
		// End the scheduling when the initial job list, the realtime job list and the user-priority job lists are empty
		if (jobListEmpty && rtListEmpty && userListsEmpty)
			done = 1;
	
		fflush(mainOutput);
	}
	
	fprintf(mainOutput, "\n -- THE END --\n");
	fflush(mainOutput);

	// Create and start threads
	/*pthread_t thDispatching, thScheduling;
	
	pthread_create(&thDispatching, NULL, threadDispatching, NULL);
	pthread_create(&thScheduling, NULL, threadScheduling, NULL);
	
	sleep(1);
	
	//attendre que les threads aient termine
	printf("Le thread principal attend que les autres se terminent\n");
	pthread_join(thDispatching, NULL);
	pthread_join(thScheduling, NULL);
	
	exit(0);*/
	
	/// -- TESTS
	//char *const params[] = {"log710h14process", NULL};
	//execvp("log710h14process", params);
	
	/*char execTime[2];
	sprintf(execTime, "%d", 5);
    execl("./log710h14process", "./log710h14process", (const char*)execTime, (char *) 0);*/
	
	/*printf("\n TESTS \n");
	
	p_list *list = (p_list*) malloc(sizeof(p_list));
	list->size = 0;
	
	proc710 p1 = {1, 0, 1, 0, 0, 0, 0, NEW, 1001};
	proc710 p2 = {2, 0, 1, 0, 0, 0, 0, NEW, 1002};
	proc710 p3 = {3, 0, 1, 0, 0, 0, 0, NEW, 1003};
	proc710 p4 = {4, 0, 1, 0, 0, 0, 0, NEW, 1004};
	proc710 p5 = {5, 0, 1, 0, 0, 0, 0, NEW, 1005};
	
	printf("size before =%d\n", list->size);
	
	// Add processes
	push(list, p1);
	push(list, p2);
	push(list, p3);
	push(list, p4);
	push(list, p5);
	
	printf("size after =%d\n", list->size);
	
	proc *currentTest = list->first;
	while (currentTest != NULL)
	{
		printf("Arrival time:%d - Pid:%d\n", currentTest->process.arrivalTime, currentTest->process.pid);
		currentTest = currentTest->next;
	}
	
	removeElem(list, 1002);
	
	printf("size now =%d\n", list->size);
	
	currentTest = list->first;
	while (currentTest != NULL)
	{
		printf("Arrival time:%d - Pid:%d\n", currentTest->process.arrivalTime, currentTest->process.pid);
		currentTest = currentTest->next;
	}
	
	printf("\n -- \n");
	
	proc710 pTest = pop(list);
	printf("Popped Arrival time %d - New first aarival time %d\n", pTest.arrivalTime, list->first->process.arrivalTime);
	printf("size now =%d\n", list->size);
	
	pTest = pop(list);
	printf("Popped Arrival time %d - New first aarival time %d\n", pTest.arrivalTime, list->first->process.arrivalTime);
	printf("size now =%d\n", list->size);
	
	pTest = pop(list);
	printf("Popped Arrival time %d - New first aarival time %d\n", pTest.arrivalTime, list->first->process.arrivalTime);
	printf("size now =%d\n", list->size);
	
	pTest = pop(list);
	printf("Popped Arrival time %d - New first aarival time NULL\n", pTest.arrivalTime, NULL);
	printf("size now =%d\n", list->size);*/
}
